
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// FIXME emphasis label position is not same with normal label position
import { parsePercent } from '../../util/number.js';
import { Point } from '../../util/graphic.js';
import { each, isNumber } from 'zrender/lib/core/util.js';
import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper.js';
import { shiftLayoutOnY } from '../../label/labelLayoutHelper.js';
var RADIAN = Math.PI / 180;

function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
	if (list.length < 2) {
		return;
	}

	function recalculateXOnSemiToAlignOnEllipseCurve(semi) {
		var rB = semi.rB;
		var rB2 = rB * rB;

		for (var i = 0; i < semi.list.length; i++) {
			var item = semi.list[i];
			var dy = Math.abs(item.label.y - cy); // horizontal r is always same with original r because x is not changed.

			var rA = r + item.len;
			var rA2 = rA * rA; // Use ellipse implicit function to calculate x

			var dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2);
			var newX = cx + (dx + item.len2) * dir;
			var deltaX = newX - item.label.x;
			var newTargetWidth = item.targetTextWidth - deltaX * dir; // text x is changed, so need to recalculate width.

			constrainTextWidth(item, newTargetWidth, true);
			item.label.x = newX;
		}
	} // Adjust X based on the shifted y. Make tight labels aligned on an ellipse curve.

	function recalculateX(items) {
		// Extremes of
		var topSemi = {
			list: [],
			maxY: 0
		};
		var bottomSemi = {
			list: [],
			maxY: 0
		};

		for (var i = 0; i < items.length; i++) {
			if (items[i].labelAlignTo !== 'none') {
				continue;
			}

			var item = items[i];
			var semi = item.label.y > cy ? bottomSemi : topSemi;
			var dy = Math.abs(item.label.y - cy);

			if (dy >= semi.maxY) {
				var dx = item.label.x - cx - item.len2 * dir; // horizontal r is always same with original r because x is not changed.

				var rA = r + item.len; // Canculate rB based on the topest / bottemest label.

				var rB = Math.abs(dx) < rA ? Math.sqrt(dy * dy / (1 - dx * dx / rA / rA)) : rA;
				semi.rB = rB;
				semi.maxY = dy;
			}

			semi.list.push(item);
		}

		recalculateXOnSemiToAlignOnEllipseCurve(topSemi);
		recalculateXOnSemiToAlignOnEllipseCurve(bottomSemi);
	}

	var len = list.length;

	for (var i = 0; i < len; i++) {
		if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
			var dx = list[i].label.x - farthestX;
			list[i].linePoints[1][0] += dx;
			list[i].label.x = farthestX;
		}
	}

	if (shiftLayoutOnY(list, viewTop, viewTop + viewHeight)) {
		recalculateX(list);
	}
}

function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
	var leftList = [];
	var rightList = [];
	var leftmostX = Number.MAX_VALUE;
	var rightmostX = -Number.MAX_VALUE;

	for (var i = 0; i < labelLayoutList.length; i++) {
		var label = labelLayoutList[i].label;

		if (isPositionCenter(labelLayoutList[i])) {
			continue;
		}

		if (label.x < cx) {
			leftmostX = Math.min(leftmostX, label.x);
			leftList.push(labelLayoutList[i]);
		} else {
			rightmostX = Math.max(rightmostX, label.x);
			rightList.push(labelLayoutList[i]);
		}
	}

	for (var i = 0; i < labelLayoutList.length; i++) {
		var layout = labelLayoutList[i];

		if (!isPositionCenter(layout) && layout.linePoints) {
			if (layout.labelStyleWidth != null) {
				continue;
			}

			var label = layout.label;
			var linePoints = layout.linePoints;
			var targetTextWidth = void 0;

			if (layout.labelAlignTo === 'edge') {
				if (label.x < cx) {
					targetTextWidth = linePoints[2][0] - layout.labelDistance - viewLeft - layout.edgeDistance;
				} else {
					targetTextWidth = viewLeft + viewWidth - layout.edgeDistance - linePoints[2][0] - layout.labelDistance;
				}
			} else if (layout.labelAlignTo === 'labelLine') {
				if (label.x < cx) {
					targetTextWidth = leftmostX - viewLeft - layout.bleedMargin;
				} else {
					targetTextWidth = viewLeft + viewWidth - rightmostX - layout.bleedMargin;
				}
			} else {
				if (label.x < cx) {
					targetTextWidth = label.x - viewLeft - layout.bleedMargin;
				} else {
					targetTextWidth = viewLeft + viewWidth - label.x - layout.bleedMargin;
				}
			}

			layout.targetTextWidth = targetTextWidth;
			constrainTextWidth(layout, targetTextWidth);
		}
	}

	adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX);
	adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX);

	for (var i = 0; i < labelLayoutList.length; i++) {
		var layout = labelLayoutList[i];

		if (!isPositionCenter(layout) && layout.linePoints) {
			var label = layout.label;
			var linePoints = layout.linePoints;
			var isAlignToEdge = layout.labelAlignTo === 'edge';
			var padding = label.style.padding;
			var paddingH = padding ? padding[1] + padding[3] : 0; // textRect.width already contains paddingH if bgColor is set

			var extraPaddingH = label.style.backgroundColor ? 0 : paddingH;
			var realTextWidth = layout.rect.width + extraPaddingH;
			var dist = linePoints[1][0] - linePoints[2][0];

			if (isAlignToEdge) {
				if (label.x < cx) {
					linePoints[2][0] = viewLeft + layout.edgeDistance + realTextWidth + layout.labelDistance;
				} else {
					linePoints[2][0] = viewLeft + viewWidth - layout.edgeDistance - realTextWidth - layout.labelDistance;
				}
			} else {
				if (label.x < cx) {
					linePoints[2][0] = label.x + layout.labelDistance;
				} else {
					linePoints[2][0] = label.x - layout.labelDistance;
				}

				linePoints[1][0] = linePoints[2][0] + dist;
			}

			linePoints[1][1] = linePoints[2][1] = label.y;
		}
	}
}
/**
 * Set max width of each label, and then wrap each label to the max width.
 *
 * @param layout label layout
 * @param availableWidth max width for the label to display
 * @param forceRecalculate recaculate the text layout even if the current width
 * is smaller than `availableWidth`. This is useful when the text was previously
 * wrapped by calling `constrainTextWidth` but now `availableWidth` changed, in
 * which case, previous wrapping should be redo.
 */

function constrainTextWidth(layout, availableWidth, forceRecalculate) {
	if (forceRecalculate === void 0) {
		forceRecalculate = false;
	}

	if (layout.labelStyleWidth != null) {
		// User-defined style.width has the highest priority.
		return;
	}

	var label = layout.label;
	var style = label.style;
	var textRect = layout.rect;
	var bgColor = style.backgroundColor;
	var padding = style.padding;
	var paddingH = padding ? padding[1] + padding[3] : 0;
	var overflow = style.overflow; // textRect.width already contains paddingH if bgColor is set

	var oldOuterWidth = textRect.width + (bgColor ? 0 : paddingH);

	if (availableWidth < oldOuterWidth || forceRecalculate) {
		var oldHeight = textRect.height;

		if (overflow && overflow.match('break')) {
			// Temporarily set background to be null to calculate
			// the bounding box without background.
			label.setStyle('backgroundColor', null); // Set constraining width

			label.setStyle('width', availableWidth - paddingH); // This is the real bounding box of the text without padding.

			var innerRect = label.getBoundingRect();
			label.setStyle('width', Math.ceil(innerRect.width));
			label.setStyle('backgroundColor', bgColor);
		} else {
			var availableInnerWidth = availableWidth - paddingH;
			var newWidth = availableWidth < oldOuterWidth // Current text is too wide, use `availableWidth` as max width.
				? availableInnerWidth : // Current available width is enough, but the text may have
			// already been wrapped with a smaller available width.
				forceRecalculate ? availableInnerWidth > layout.unconstrainedWidth // Current available is larger than text width,
				// so don't constrain width (otherwise it may have
				// empty space in the background).
					? null // Current available is smaller than text width, so
				// use the current available width as constraining
				// width.
					: availableInnerWidth : // Current available width is enough, so no need to
				// constrain.
					null;
			label.setStyle('width', newWidth);
		}

		var newRect = label.getBoundingRect();
		textRect.width = newRect.width;
		var margin = (label.style.margin || 0) + 2.1;
		textRect.height = newRect.height + margin;
		textRect.y -= (textRect.height - oldHeight) / 2;
	}
}

function isPositionCenter(sectorShape) {
	// Not change x for center label
	return sectorShape.position === 'center';
}

export default function pieLabelLayout(seriesModel) {
	var data = seriesModel.getData();
	var labelLayoutList = [];
	var cx;
	var cy;
	var hasLabelRotate = false;
	var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN;
	var viewRect = data.getLayout('viewRect');
	var r = data.getLayout('r');
	var viewWidth = viewRect.width;
	var viewLeft = viewRect.x;
	var viewTop = viewRect.y;
	var viewHeight = viewRect.height;

	function setNotShow(el) {
		el.ignore = true;
	}

	function isLabelShown(label) {
		if (!label.ignore) {
			return true;
		}

		for (var key in label.states) {
			if (label.states[key].ignore === false) {
				return true;
			}
		}

		return false;
	}

	data.each(function (idx) {
		var sector = data.getItemGraphicEl(idx);
		var sectorShape = sector.shape;
		var label = sector.getTextContent();
		var labelLine = sector.getTextGuideLine();
		var itemModel = data.getItemModel(idx);
		var labelModel = itemModel.getModel('label'); // Use position in normal or emphasis

		var labelPosition = labelModel.get('position') || itemModel.get(['emphasis', 'label', 'position']);
		var labelDistance = labelModel.get('distanceToLabelLine');
		var labelAlignTo = labelModel.get('alignTo');
		var edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth);
		var bleedMargin = labelModel.get('bleedMargin');
		var labelLineModel = itemModel.getModel('labelLine');
		var labelLineLen = labelLineModel.get('length');
		labelLineLen = parsePercent(labelLineLen, viewWidth);
		var labelLineLen2 = labelLineModel.get('length2');
		labelLineLen2 = parsePercent(labelLineLen2, viewWidth);

		if (Math.abs(sectorShape.endAngle - sectorShape.startAngle) < minShowLabelRadian) {
			each(label.states, setNotShow);
			label.ignore = true;

			if (labelLine) {
				each(labelLine.states, setNotShow);
				labelLine.ignore = true;
			}

			return;
		}

		if (!isLabelShown(label)) {
			return;
		}

		var midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2;
		var nx = Math.cos(midAngle);
		var ny = Math.sin(midAngle);
		var textX;
		var textY;
		var linePoints;
		var textAlign;
		cx = sectorShape.cx;
		cy = sectorShape.cy;
		var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';

		if (labelPosition === 'center') {
			textX = sectorShape.cx;
			textY = sectorShape.cy;
			textAlign = 'center';
		} else {
			var x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx;
			var y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy;
			textX = x1 + nx * 3;
			textY = y1 + ny * 3;

			if (!isLabelInside) {
				// For roseType
				var x2 = x1 + nx * (labelLineLen + r - sectorShape.r);
				var y2 = y1 + ny * (labelLineLen + r - sectorShape.r);
				var x3 = x2 + (nx < 0 ? -1 : 1) * labelLineLen2;
				var y3 = y2;

				if (labelAlignTo === 'edge') {
					// Adjust textX because text align of edge is opposite
					textX = nx < 0 ? viewLeft + edgeDistance : viewLeft + viewWidth - edgeDistance;
				} else {
					textX = x3 + (nx < 0 ? -labelDistance : labelDistance);
				}

				textY = y3;
				linePoints = [[x1, y1], [x2, y2], [x3, y3]];
			}

			textAlign = isLabelInside ? 'center' : labelAlignTo === 'edge' ? nx > 0 ? 'right' : 'left' : nx > 0 ? 'left' : 'right';
		}

		var PI = Math.PI;
		var labelRotate = 0;
		var rotate = labelModel.get('rotate');

		if (isNumber(rotate)) {
			labelRotate = rotate * (PI / 180);
		} else if (labelPosition === 'center') {
			labelRotate = 0;
		} else if (rotate === 'radial' || rotate === true) {
			var radialAngle = nx < 0 ? -midAngle + PI : -midAngle;
			labelRotate = radialAngle;
		} else if (rotate === 'tangential' && labelPosition !== 'outside' && labelPosition !== 'outer') {
			var rad = Math.atan2(nx, ny);

			if (rad < 0) {
				rad = PI * 2 + rad;
			}

			var isDown = ny > 0;

			if (isDown) {
				rad = PI + rad;
			}

			labelRotate = rad - PI;
		}

		hasLabelRotate = !!labelRotate;
		label.x = textX;
		label.y = textY;
		label.rotation = labelRotate;
		label.setStyle({
			verticalAlign: 'middle'
		}); // Not sectorShape the inside label

		if (!isLabelInside) {
			var textRect = label.getBoundingRect().clone();
			textRect.applyTransform(label.getComputedTransform()); // Text has a default 1px stroke. Exclude this.

			var margin = (label.style.margin || 0) + 2.1;
			textRect.y -= margin / 2;
			textRect.height += margin;
			labelLayoutList.push({
				label: label,
				labelLine: labelLine,
				position: labelPosition,
				len: labelLineLen,
				len2: labelLineLen2,
				minTurnAngle: labelLineModel.get('minTurnAngle'),
				maxSurfaceAngle: labelLineModel.get('maxSurfaceAngle'),
				surfaceNormal: new Point(nx, ny),
				linePoints: linePoints,
				textAlign: textAlign,
				labelDistance: labelDistance,
				labelAlignTo: labelAlignTo,
				edgeDistance: edgeDistance,
				bleedMargin: bleedMargin,
				rect: textRect,
				unconstrainedWidth: textRect.width,
				labelStyleWidth: label.style.width
			});
		} else {
			label.setStyle({
				align: textAlign
			});
			var selectState = label.states.select;

			if (selectState) {
				selectState.x += label.x;
				selectState.y += label.y;
			}
		}

		sector.setTextConfig({
			inside: isLabelInside
		});
	});

	if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
		avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
	}

	for (var i = 0; i < labelLayoutList.length; i++) {
		var layout = labelLayoutList[i];
		var label = layout.label;
		var labelLine = layout.labelLine;
		var notShowLabel = isNaN(label.x) || isNaN(label.y);

		if (label) {
			label.setStyle({
				align: layout.textAlign
			});

			if (notShowLabel) {
				each(label.states, setNotShow);
				label.ignore = true;
			}

			var selectState = label.states.select;

			if (selectState) {
				selectState.x += label.x;
				selectState.y += label.y;
			}
		}

		if (labelLine) {
			var linePoints = layout.linePoints;

			if (notShowLabel || !linePoints) {
				each(labelLine.states, setNotShow);
				labelLine.ignore = true;
			} else {
				limitTurnAngle(linePoints, layout.minTurnAngle);
				limitSurfaceAngle(linePoints, layout.surfaceNormal, layout.maxSurfaceAngle);
				labelLine.setShape({
					points: linePoints
				}); // Set the anchor to the midpoint of sector

				label.__hostTarget.textGuideLineConfig = {
					anchor: new Point(linePoints[0][0], linePoints[0][1])
				};
			}
		}
	}
}